/*************************************************************************
 * Copyright 2008 David Almilli
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0 
 *      
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 **************************************************************************/
package com.tivo.hme.bananas;

import static com.tivo.hme.bananas.IBananasPlus.*;

import com.tivo.hme.sdk.Resource;
import java.awt.Rectangle;
import java.lang.reflect.Field;
import java.util.Vector;

public class BListPlus<E> extends BList implements PlusView {
    private boolean disposeResourceOnRemove;
	private boolean highlightInit;
    
    private Vector<BView> rows;
    protected int rowHeight;
	protected int rowIndent;
	protected Object color;
	protected Object font;
	protected BHighlightsPlus rowHighlights;
	protected boolean animate;
	protected boolean ignoreRefreshes;
    protected ListRenderer<E> listRenderer;

	public BListPlus(BView parent, int x, int y, int width, int height, int rowHeight) {
		this(parent, x, y, width, height, rowHeight, true);
	}

	public BListPlus(BView parent, int x, int y, int width, int height, int rowHeight,
			boolean visible) {
		this(parent, ViewUtils.getBounds(parent, x, y, width, height), rowHeight, visible);
	}

	public BListPlus(BView parent, int x, int y, int width, int height) {
		this(parent, x, y, width, height, true);
	}

	public BListPlus(BView parent, int x, int y, int width, int height,
			boolean visible) {
		this(parent, ViewUtils.getBounds(parent, x, y, width, height), 
                ViewUtils.getHeight(parent, H_BAR), visible);
	}

    public BListPlus(BView parent, com.tivo.hme.bananas.layout.Layout layout) {
        this(parent, layout.getBounds(), ViewUtils.getHeight(parent, H_BAR), true);
    }

    public BListPlus(BView parent, com.tivo.hme.bananas.layout.Layout layout, int rowHeight, boolean visible) {
        this(parent, layout.getBounds(), rowHeight, visible);
    }

	protected BListPlus(BView parent, Rectangle bounds, int rowHeight, boolean visible) {
		super(parent, bounds.x, bounds.y, bounds.width, bounds.height, rowHeight, visible);
        this.rowHeight = rowHeight;
	}

    @Override
    public void remove(Resource animation) {
        PlusSupport.viewRemoveNotify(this);
        super.remove(animation);
    }
    
    protected void viewRemoveNotify() {
        if (getDisposeResourceOnRemove()) {
            Resource resource = getResource();
            if (resource != null) {
                //remove the resource
                resource.remove();
            }
        }
    }

    public boolean getDisposeResourceOnRemove() {
        return disposeResourceOnRemove;
    }

    public void setDisposeResourceOnRemove(boolean disposeResourceOnRemove) {
        this.disposeResourceOnRemove = disposeResourceOnRemove;
    }
    
    public Rectangle getScreenFocusBounds() {
        BPoint s = toScreen();
        return new Rectangle(s.x, s.y, getWidth(), getHeight());
    }
    
    @SuppressWarnings("unchecked")
    protected Vector<BView> getRows() {
        if (this.rows == null) {
            //get a pointer to the rows list
            try {
                Field field = BList.class.getDeclaredField("rows");
                field.setAccessible(true);
                this.rows = (Vector)field.get(this);
            } catch (Exception e) {
                throw new RuntimeException("Unable to obtain rows reference.", e);
            }
        }
        return this.rows;
    }
	
	protected BHighlightsPlus createRowHighlights() {
		return new BHighlightsPlus(new ListLayout());
	}
	
	public BHighlightsPlus getRowHighlights() {
		if (rowHighlights == null) {
			rowHighlights = createRowHighlights();
		}
		return rowHighlights;
	}
	
	public void setRowHighlights(BHighlightsPlus rowHighlights) {
		this.rowHighlights = rowHighlights;
		refresh();
	}

	protected boolean isIgnoreRefreshes() {
		return ignoreRefreshes;
	}

	protected void setIgnoreRefreshes(boolean ignoreRefreshes) {
		this.ignoreRefreshes = ignoreRefreshes;
	}

	protected boolean isAnimate() {
		return animate;
	}

	protected void setAnimate(boolean animate) {
		this.animate = animate;
	}
	
    @Override
    @SuppressWarnings("unchecked")
    public E get(int index) {
        return (E)super.get(index);
    }

    @Override
    @SuppressWarnings("unchecked")
    public E remove(int index) {
        return (E)super.remove(index);
    }

    @Override
    @SuppressWarnings("unchecked")
    public E set(int index, Object element) {
        return (E)super.set(index, element);
    }

    /**
     * Get the highlights.
     *
     * <p>This is a thread-safe public entry point.</p>
     */
    @Override
    public BHighlightsPlus getHighlights() {
    	if (!highlightInit) {
    		highlightInit = true;
    		super.setHighlights(new BHighlightsPlus(this));
    	}
    	return (BHighlightsPlus)super.getHighlights();
    }
    
    /**
     * Overridden to enforce the use of BHighlightsPlus.
     * @throws IllegalArgumentException if the highlights aren't an instance of BHighlightsPlus
     */
    @Override
    public void setHighlights(BHighlights highlights) throws IllegalArgumentException {
    	if (!(highlights instanceof BHighlightsPlus)) {
    		throw new IllegalArgumentException("You must use BHighlightsPlus with this view.");
    	}
    	super.setHighlights(highlights);
    }

    /**
     * Overridden to support anchoring inside the parent view.
     */
	@Override
	public void setBounds(int x, int y, int width, int height, Resource animation) {
		Rectangle bounds = ViewUtils.getBounds(getParent(), x, y, width, height);
		super.setBounds(bounds.x, bounds.y, bounds.width, bounds.height, animation);
	}
    
    public void setBounds(com.tivo.hme.bananas.layout.Layout layout, Resource animation) {
        Rectangle bounds = layout.getBounds();
        super.setBounds(bounds.x, bounds.y, bounds.width, bounds.height, animation);
    }
    
    public void setBarAndArrows(int bar_left, int bar_right, Object action_left, Object action_right) {
        super.setBarAndArrows(bar_left, bar_right, action_left, action_right);
        setUpDownArrows(getRowHighlights(), bar_left, bar_right);
    }

    protected void setUpDownArrows(BHighlights h, int bar_left, int bar_right) {
        //show the up down arrows in the safe action area if the bar is hanging
        if (bar_left == BAR_HANG || bar_right == BAR_HANG) {
            BSkin skin = getBApp().getSkin();
            BSkin.Element up = skin.get(H_UP);
            BSkin.Element down = skin.get(H_DOWN);
            BRect rect = h.getRect();
            
            int safeTitleH = ((BApplicationPlus)getBApp()).getSafeTitleHorizontal();

            int originx = -rect.x;
            
            if (bar_left == BAR_HANG) {
                int whi_up = originx + safeTitleH - up.getWidth();
                int whi_down = originx + safeTitleH - down.getWidth();
                h.setWhisperingArrow(H_UP, A_LEFT + whi_up, A_TOP-up.getHeight(), null);
                h.setWhisperingArrow(H_DOWN, A_LEFT + whi_down, A_BOTTOM+down.getHeight(), null);
                
            } else if (bar_right == BAR_HANG) {
                int offset = getScreen().getWidth() + originx - safeTitleH;
                h.setWhisperingArrow(H_UP, A_LEFT + offset, A_TOP-up.getHeight(), null);
                h.setWhisperingArrow(H_DOWN, A_LEFT + offset, A_BOTTOM+down.getHeight(), null);
            }
        }
    }

    /*
     * returns first selectable index starting from passed startIndex (and decrementing if
     * necessary). if none found will return startIndex+1, which is assumed to be the current index
     * position
     */
    protected int getPreviousSelectable(int startIndex){
        int i;
        for (i=startIndex; i>=0; i--){
            if (((BListItemExtensions)get(i)).isSelectable()){
                return i;
            }
        }
        return startIndex+1;       
    }
    
    /*
     * returns first selectable index starting from passed startIndex (and incrementing if necessary).
     * if none found will return startIndex-1, which is assumed to be the current index position
     */
    protected int getNextSelectable(int startIndex){
        int i;
        for (i=startIndex; i<size(); i++){
            if (((BListItemExtensions)get(i)).isSelectable()){
                return i;
            }
        }
        return startIndex-1;  
    }
	
	public int getRowIndent() {
		return rowIndent;
	}

	public void setRowIndent(int rowCap) {
		this.rowIndent = rowCap;
	}
    
    public int getRowIndent(int bar, boolean arrow) {
        BSkinPlus skin = (BSkinPlus)getBApp().getSkin();
        BSkinPlus.Element e = skin.get(H_BAR);
        BSkinPlus.Element a = skin.get(H_LEFT);
        int indent = 0;
        if (bar != BAR_HANG) {
            indent += e.getInt(P_PAD_H, 10);
            if (arrow) {
                indent += a.getWidth();
            }
        }
        return indent;
    }
	
	public Integer getDefaultRowCap() {
		BSkinPlus skin = (BSkinPlus)getBApp().getSkin();
		BSkinPlus.Element e = skin.get(H_BAR);
		return e.getInt(P_CAP);
	}
	
	public Integer getDefaultRowPadH() {
		BSkinPlus skin = (BSkinPlus)getBApp().getSkin();
		BSkinPlus.Element e = skin.get(H_BAR);
		return e.getInt(P_PAD_H);
	}
	
	public Integer getDefaultRowPadV() {
		BSkinPlus skin = (BSkinPlus)getBApp().getSkin();
		BSkinPlus.Element e = skin.get(H_BAR);
		return e.getInt(P_PAD_V);
	}
	
	public Resource getDefaultFont() {
		BSkin skin = getBApp().getSkin();
		BSkin.Element e = skin.get(H_BAR_FONT);
		if (e != null) {
			return e.getResource();
		} else {
			return null;
		}
	}
	
	public Resource getDefaultTextColor() {
		BSkin skin = getBApp().getSkin();
		BSkin.Element e = skin.get(H_BAR_TEXT_COLOR);
		if (e != null) {
			return e.getResource();
		} else {
			return null;
		}
	}

	public Object getColor() {
        if (color == null) {
            setColor(getDefaultTextColor());
        }
		return color;
	}

	public void setColor(Object textColor) {
		this.color = textColor;
	}

	public Object getFont() {
        if (font == null) {
            setFont(getDefaultFont());
        }
		return font;
	}

	public void setFont(Object font) {
		this.font = font;
	}


    /**
     * Get a row at the specific index, creating it if necessary.
     *
     * <p>This is a thread-safe public entry point.</p>
     */
    @Override
    public BView getRow(int index) {

        // Since this is our main accessor this a good place to make sure that
        // we move the row to the correct height.
        Vector<BView> rows = getRows();
        BView view = rows.get(index);
        if (view == null) {
            view = createRowContainer(index, 0, index * rowHeight, getWidth(), rowHeight);
            view.setFocusable(true);
            view.setHighlights(getRowHighlights());
            handleCreateRow(view, index);
            rows.set(index, view);
        } else {
            view.setLocation(view.getX(), index * rowHeight);
        }
        return view;
    }

    protected void handleCreateRow(BView parent, int index) {
        createRow(parent, index);
    }
    
    protected BView createRowContainer(int index, int x, int y, int width, int height) {
        BHighlightsPlus h = getRowHighlights();
        int left = getRowIndent(h.getBarLeft(), h.isArrowInside());
        int right = getRowIndent(h.getBarRight(), h.isArrowInside());
        
        RowView view = new RowView(this, x+left, y, width-left-right, height);
        return view;
    }

    public boolean handleKeyPress(int code, long rawcode) {
    	int top = getTop();
    	boolean result = super.handleKeyPress(code, rawcode);
    	int newtop = getTop();
    	if (getFocus() != -1) {
            if (newtop != top) {
                animate = true;
            }
    	}
    	return result;
    }

    public void setFocus(int index, boolean animate) {
		this.animate = animate;
		super.setFocus(index, animate);
		this.animate = false;
    }
    
    public E getFocusObject() {
        int focus = getFocus();
        if (focus == -1) {
            return null;
        } else {
            return get(focus);
        }
    }
    
    /**
     * Reloads the data/view for a single row.
     * @param row the index of the row to reload
     */
    public void reloadRow(int row) {
        setIgnoreRefreshes(true);
        try {
            int focus = getFocus();
            E value = remove(row);
            add(row, value);
            setFocus(focus, false);
        } finally {
            setIgnoreRefreshes(false);
        }
    }
    
    /**
     * Reloads the data/views for all the rows between the start and end index (inclusive).
     * @param startRow the starting index of the range of rows to reload
     * @param endRow the ending index of the range of rows to reload
     */
    public void reloadRows(int startRow, int endRow) {
        setIgnoreRefreshes(true);
        try {
            int focus = getFocus();
            for (int row=startRow; row <= endRow; row++) {
                E value = remove(row);
                add(row, value);
            }
            setFocus(focus, false);
        } finally {
            setIgnoreRefreshes(false);
        }
    }
    
    public void refresh() {
    	if (isIgnoreRefreshes()) {
    		return;
    	}
    	super.refresh();

        if (getFocus() != -1) {
            Resource anim = animate ? getResource(ANIM) : null;
            animate = false;
        	getRowHighlights().refresh(anim);
        }
    }

    public ListRenderer<E> getListRenderer() {
        if (listRenderer == null) {
            listRenderer = new DefaultListRenderer<E>();
        }
        return listRenderer;
    }

    public void setListRenderer(ListRenderer<E> listRenderer) {
        this.listRenderer = listRenderer;
    }

    /**
     * Creates a rendered row attached to the parent.  This uses the list renderer to create
     * the row.
     * @param parent the container to build the row view from
     * @param index the index of the row being created
     */
    @Override
    protected void createRow(BView parent, int index) {
        getListRenderer().createRow(this, parent, index);
    }
    
    /**
     * Disposes a previously rendered row.  This method will give the developer a chance to clean
     * up any resources associated with the row.
     * @param parent the parent container that the row was built from
     */
    protected void disposeRow(BView parent) {
        getListRenderer().disposeRow(this, parent);
    }

	/**
	 * The <code>Layout</code> class is provided to expose outside the package
	 * 
	 * @author almilli
	 */
	public class ListLayout extends BList.Layout {
		public ListLayout() {}

        public BRect getHighlightBounds() {
            BView row = getRow();
            if (row != null) {
                return toScreenBounds(new BRect(0, row.getY(), getWidth(), rowHeight));
                //return row.getHighlightBounds();
            }
            return toScreenBounds(new BRect(0, 0, getWidth(), rowHeight));
        }
	}
    
    protected class RowView extends BViewPlus {

        public RowView(BView parent, int x, int y, int width, int height, boolean visible) {
            super(parent, x, y, width, height, visible);
        }

        public RowView(BView parent, int x, int y, int width, int height) {
            super(parent, x, y, width, height);
        }

        public RowView(BView parent, com.tivo.hme.bananas.layout.Layout layout, boolean visible) {
            super(parent, layout, visible);
        }

        public RowView(BView parent, com.tivo.hme.bananas.layout.Layout layout) {
            super(parent, layout);
        }

        public RowView(BView parent, Rectangle bounds, boolean visible) {
            super(parent, bounds, visible);
        }

        @Override
        protected void viewRemoveNotify() {
            disposeRow(this);
            super.viewRemoveNotify();
        }
    }
}
